#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on 20101114
Author: zkz
"""

import web
import json
import time
import math
import signal
import re
import fcntl
import errno
import os
import math
import sys
import uuid
import logging
import inspect
import traceback
import subprocess
import threading
import urllib
import httplib
import json
import datetime
import functools

from contextlib import contextmanager

import Umpweb
from Umpweb.db import api as db_api
from Umpweb.common import exception


LOG = logging.getLogger('Umpweb.common.utils')


xx = lambda x: '%.2f'%(float(x)) if x is not None else 0
yy = lambda x, y: math.ceil(x/float('%.f'%(y))) if x is not None else 0
xx4 = lambda x: '%.4f'%(float(x)) if x is not None else 0

base = [str(x) for x in range(10)] + [ chr(x) for x in range(ord('A'),ord('A')+6)]
home = os.path.abspath(os.path.split(os.path.realpath(__file__))[0] + "/../../..")

def get_install_dir():
    return os.path.abspath(os.path.split(os.path.realpath(Umpweb.__file__))[0] + "/../../..")

install_path = get_install_dir() 

byte2GB = lambda byte, unit=1024: float(byte) /unit /unit /unit

def current_dir():
    return os.path.dirname(__file__)

def content_replace(file_path, tobe_prepared, prepared_by):
    c = open(file_path).read()
    c = re.sub(tobe_prepared, prepared_by, c)
    open(file_path, 'wb').write(c)

def ensure_dir(dir_f):
    """如果没有创建目录"""
    dir_ = os.path.dirname(dir_f)
    if not os.path.isdir(dir_):
        LOG.info('*** make dir: %s' % (dir_))
        os.makedirs(dir_)

def login_required(func):
    def new_func(*args, **argvkw):
        session = web.ctx.session
        url =  web.ctx.home
        urls = url.split(':')
        port = urls[-1].split('/')[0]
        home = urls[:-1]
        home.append(port)
        res = ':'.join(home)
        web.ctx.home = res
        if 'user' in session.keys() and session.user.get('is_login', 0) == 1:
            return func(*args, **argvkw)
        else:
            return web.seeother('/login')
    return new_func


def local_exec(cmd):
    try:
        p = subprocess.Popen(args=cmd,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             close_fds=True,
                             shell=True)
        p.wait()
        out = p.stdout.read()
        err = p.stderr.read()
        return out, err
    except:
        raise

def str2list(_str):
    '''str以空格为分割符号'''
    sstr = _str.strip()
    if sstr == '':
        return []
    else:
        return sstr.split(' ')

def str_list(_str,sign):
    '''str以sign为分割符号'''
    sstr = _str.strip()[:-1]
    if sstr == '':
        return []
    else:
        
        return sstr.split(sign)


def filename():
    return inspect.currentframe().f_code.co_filename


def lineno():
    """Return the current line number in our program"""
    return inspect.currentframe().f_back.f_lineno


def conv_float(x, decimal=2):
    #只保留2位，不进位
    assert(isinstance(x, float))
    a, b = str(x).split('.')
    return float(a + '.' + b[:decimal])

#TODO 把query换成db/api/model_query()
def grid_rows_query(table, web_input, filters={}, read_deleted='no', filter_not_equal={}):
    params = {'table':table,'filter_by':filters,'mode':'count'}
    
    records = db_api.db_count(params)
    limit = int(web_input.rows)  #每页显示行。
    sord = web_input.sord        #升序还是降序
    sidx = web_input.sidx        #排序关键字
    page = int(web_input.page)   #申请的第几页
    offset = limit*(page-1)
    total = math.ceil(records/float(limit))
    total = int(total)

    params = {
        'table': table,
        'filter_by': filters,
        'filter_not_equal': filter_not_equal,
        'mode': 'all'
    }
    params['order_by'] = '%s %s'%(sidx, sord.lower())
    params['limit'] = limit
    params['offset'] = offset
    rows = db_api.db_query(params)
    return (page, total, records, rows)

def grid_json(page, total, records, rows, id_attr, cells=[]):
    list_rows = []
    for i in rows:
        id_ = getattr(i, id_attr)
        cell = []
        for j in cells:
            value = str(getattr(i, j, None))
            if j in ['created_at', 'updated_at', \
                    'launched_at', 'record_at', \
                    'start_at', 'finished_at', 'terminated_at']:
                value = value.partition('.')[0]
            if value == 'None':
                value = ''
            cell.append(value)

    #cell = [str(getattr(i, j, None)) for j in cells]
        list_rows.append({"id":id_, "cell":cell})
    res={"page": str(page),
        "total": str(total),
        "records": str(records),
        "rows": list_rows }
    return json.dumps(res)


def _str2dict(s):
    if (s[-1] == '\n'):
        s = s[:-1]

    a = s.split('\n')
    d = {}
    a = [i for i in a if i]
    for i in a:
        p = i.split(':')
        if (d.get(p[0])):
            raise  Exception("dup key exist")
        try:
            d[p[0].strip()] = p[1].strip()
        except IndexError as err:
            LOG.info("str %s" % (s))
            raise
    return d


def get_current_user():
    return db_api.user_get_with_name(web.config._session.user)


def _human_readable(num, idx=0):
    u = ['B', 'K', 'M', 'G', 'T']
    LOG.info("num %u idx %u" % (num, idx))
    if (num / 1024 < 1):
        return str(int(num)) + u[idx]
    else:
        idx_ = idx+1
        if idx_ > 4:
            idx_ =idx
        return _human_readable(num / 1024, idx_ )
        return _human_readable(num / 1024, idx + 1)


def get_tb(val):
    if val > 1000:
        val = "%.1f T"%float(int(val)/1000)
    else:
        val = "%sG"%int(val)
    return val


def alert_handler(signum, frame):
    raise Exception("command execute time out")

                 
def _lock_file1(key, timeout=1, p=False):
    key = os.path.abspath(key)
    parent = os.path.split(key)[0]
    os.system("mkdir -p " + parent)


    if p:
        LOG.info ("get lock %s" %(key))
    lock_fd = open(key, 'a')

    if timeout != 0:
        signal.signal(signal.SIGALRM, alert_handler)
        signal.alert(timeout)
    try:
        fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX)
        signal.alert(0)
        if p:
            LOG.info ("lock %s success" %(key))
    except Exception, e:
        raise Exception("lock %s failed" %(key))

    return lock_fd

def _unlock_file1(lock_fd):
    fcntl.flock(lock_fd.fileno(), fcntl.LOCK_UN)


def uuid4():
    return str(uuid.uuid4()).replace('-', '')


def get_exception_stacktrace():
    return traceback.format_exc()


def exception2json(error):
    '''return program error to ui,format is json.
    '''
    data =  {"reply": {"is_success":False, "error":"%s"%error}}
    return json.dumps(data)

def user_relogin(token):
    '''To request token without username.

    Trigger when the user is login in UI session and controller'token is expired
    '''
    session = web.config._session
    if not (hasattr(session, 'user') and session.user.is_login == 1): 
        return token 

    username = session.user.name
    values = {'username':username, 'without_password':True,}
    body = {'Login':{'params':values}}
    try:
        reply = web.config._server.api_sync_call(body)
        reply = json.loads(reply)
    except Exception, e:
        print e
        raise

    if reply.get('reply', {}).get('is_success', False):
        session.user.name = username
        records = reply.get('records', {}) 
        session.user.id = records.get('user_id') 
        session.user.token = records.get('token') 
        session.user.is_login = 1 
        return records.get('token') 
    else:
        raise exception.AuthenticationFailed("管理节点认证失效，请重新登录") 


def retry(retry_number=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kw):
            attempt = 0 
            while attempt < retry_number:
                try:
                    return func(self, *args, **kw)
                except Exception, e:
                    attempt += 1
            raise 
        return wrapper
    return decorator


def _exec_http(msg, host='127.0.0.1', port=27914, 
                        url="/fusionstor_pipe", method="POST", token=None, retry=3):
    data = {}
    try:
        values = {'msg':json.dumps(msg)}
        params = urllib.urlencode(values)
        headers = {
            "Content-type": "application/x-www-form-urlencoded",
        }
        if token:
            headers["X_AUTHORIZATION"] = "token=%s" % token

        conn = httplib.HTTPConnection(host, port)
        conn.request(method, url, params, headers)
        response = conn.getresponse()
        if response.status == 401 :
            raise exception.AuthenticationFailed("controller need request contain a valid token")

        data = response.read()
        data = json.loads(data)
        if data :
            data = data.values()[0]
        conn.close()
    except exception.AuthenticationFailed: 
        token = user_relogin(token)
        raise exception.AuthenticationFailed("管理节点认证失效，请重新登录")
    except Exception, e:
        print e
        traceback.print_exc()
        raise Exception('无法连接管理节点，请确认管理节点已启动.')
    finally:
        if 'conn' in locals().keys():
            conn.close()
    return data 


def _execute_http(msg, host='127.0.0.1', port=27914, 
                        url="/fusionstor_pipe", method="POST", token=None, retry=3):
    data = {}
    try:
        values = json.dumps(msg)
        params = values
        headers = {
            "Content-type": "application/x-www-form-urlencoded",
        }
        if token:
            headers["X_AUTHORIZATION"] = "token=%s" % token

        conn = httplib.HTTPConnection(host, port)
        conn.request(method, url, params, headers)
        response = conn.getresponse()
        if response.status == 401 :
            raise exception.AuthenticationFailed("controller need request contain a valid token")

        data = response.read()
        conn.close()
    except exception.AuthenticationFailed: 
        #token = user_relogin(token)
        raise exception.AuthenticationFailed("管理节点认证失效，请重新登录")
    except Exception, e:
        print e
        traceback.print_exc()
        raise Exception('无法连接管理节点，请确认管理节点已启动.')
    finally:
        if 'conn' in locals().keys():
            conn.close()
    return data 


class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, datetime.date):
            return obj.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, obj)


def date2json(obj):
    obj = json.dumps(obj, cls=DateEncoder)
    return obj


def web_return(*args, **kwargs):
    try:
        reply = web.config._server.api_sync_call(*args, **kwargs)
    except Exception, e:
        traceback.print_exc()
        return exception2json(e)
    return reply


def join_object_keys(objs, key, sep=","):
    keys = [ getattr(xx, key) for xx in objs ]
    return sep.join(keys)


def retains_decimal(val, decimal=0):
    prefix = "%.f" 
    if decimal > 0:
        prefix = "%.%sf" % decimal
    val = prefix % val
    return val

    
def percent(dividend, divisor, default=100, decimal=0):
    '''
        2/0 = default
        None/1 = default
        result type float 
    '''
    quotient = default
    if not dividend or not divisor:
        return default
    if int(divisor) != 0:
        quotient = float(dividend) / float(divisor) * 100
    quotient = retains_decimal(quotient)
    return quotient
    
def _exec_pipe(cmd, retry = 3, p = False, timeout = 0, is_raise=False):
    env = {"LANG" : "en_US", "LC_ALL" : "en_US", "PATH" : os.getenv("PATH")}
    #cmd = self.lich_inspect + " --movechunk '%s' %s  --async" % (k, loc)
    _retry = 0
    cmd1 = ''
    for i in cmd:
        cmd1 = cmd1 + i + ' '
    if (p):
        LOG.info('cmd1 %s' % (cmd1))
    while (1):
        p = None
        try:
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env = env)
        except Exception, e:
            raise Exception(e)

        if timeout != 0:
            signal.signal(signal.SIGALRM, alarm_handler)
            signal.alarm(timeout)
        try:
            stdout, stderr = p.communicate()
            signal.alarm(0)
            ret = p.returncode
            if (ret == 0):
                return ret, stdout, stderr
            elif (ret == errno.EAGAIN and _retry < retry):
                _retry = _retry + 1
                time.sleep(1)
                continue
            else:
                if is_raise:
                    raise Exception(stderr)
                return ret, stdout, stderr

        except KeyboardInterrupt as err:
            p.kill()
            raise Exception(err)
            exit(errno.EINTR)
